using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class SplineCurve :MonoBehaviour {

    public static int[] intervals;
    public static Transform[] array;

  public void Start()
  {

  }

  public static AnimationClip getAnimationBSpline(Transform[] arrayPos, int degree = 3, int resolution = 100, float time = 1.0f)
  {
    AnimationClip clip = new AnimationClip();
    float current = 0;
    AnimationCurve curveX = new AnimationCurve();
    AnimationCurve curveY = new AnimationCurve();
    AnimationCurve curveZ = new AnimationCurve();

    Vector3[] tab = bSpline(arrayPos, degree, resolution);

    curveX.AddKey(0, arrayPos[0].position.x);
    curveY.AddKey(0, arrayPos[0].position.y);
    curveZ.AddKey(0, arrayPos[0].position.z);

    Vector3 currentPos = new Vector3(arrayPos[0].position.x, arrayPos[0].position.y, arrayPos[0].position.z);
    float totalDistance = 0;

    foreach (Vector3 vec in tab)
    {        
        totalDistance += Vector3.Distance(currentPos, vec);
        currentPos = vec;
    }

    float currentDistance = 0;
    currentPos = new Vector3(arrayPos[0].position.x, arrayPos[0].position.y, arrayPos[0].position.z);

    foreach (Vector3 vec in tab)
    {
        current = currentDistance * time / totalDistance;
        curveX.AddKey(current, vec.x);
        curveY.AddKey(current, vec.y);
        curveZ.AddKey(current, vec.z);

        currentDistance += Vector3.Distance(currentPos, vec);
        currentPos = vec;
    }

    clip.SetCurve("", typeof(Transform), "localPosition.x", curveX);
    clip.SetCurve("", typeof(Transform), "localPosition.y", curveY);
    clip.SetCurve("", typeof(Transform), "localPosition.z", curveZ);

    return clip;
  }

  public static void DrawDebugSpline(Transform[] arrayPos, Color color, int degree = 3, int resolution = 100)
  {
      Vector3[] tab = bSpline(arrayPos, degree, resolution);

      int nbNode = tab.Length;
      for (int i = 0; i < nbNode - 1; ++i)
      {
          Debug.DrawLine(tab[i], tab[i + 1], color);
      }

  }

  public static Vector3[] GetTabPos(Transform[] arrayPos, Color color, int degree = 3, int resolution = 100)
  {
      Vector3[] tab = bSpline(arrayPos, degree, resolution);

      return tab;

  }


  private static Vector3[] bSpline(Transform[] arrayPos, int degree, int resolution)
  {
    Vector3[] result = new Vector3[resolution];

    intervals = computeIntervals(arrayPos.Length - 1, degree);

    float step = (float)((arrayPos.Length - 1 - degree + 2) / (float)(resolution - 1));

    float current = 0;

    for (int i = 0; i < resolution - 1; i++)
    {
      result[i] = bSplineValue(intervals, degree, current, arrayPos);

      
      current += step;
    }

    result[resolution - 1] = new Vector3(arrayPos[arrayPos.Length - 1].position.x, arrayPos[arrayPos.Length - 1].position.y, arrayPos[arrayPos.Length - 1].position.z);


    return result;
  }

  public static Vector3[] bSpline(Vector3[] arrayPos, int degree, int resolution)
  {
      Vector3[] result = new Vector3[resolution];

      intervals = computeIntervals(arrayPos.Length - 1, degree);

      float step = (float)((arrayPos.Length - 1 - degree + 2) / (float)(resolution - 1));

      float current = 0;

      for (int i = 0; i < resolution - 1; i++)
      {
          result[i] = bSplineValue(intervals, degree, current, arrayPos);
          current += step;
      }

      result[resolution - 1] = new Vector3(arrayPos[arrayPos.Length - 1].x, arrayPos[arrayPos.Length - 1].y, arrayPos[arrayPos.Length - 1].z);


      return result;
  }

  private static int[] computeIntervals(int nbPoints, int degree)
  {
    int[] intervaleu = new int[nbPoints + degree + 1];

    for (int i = 0; i < nbPoints + degree; i++)
    {
      if (i < degree)
        intervaleu[i] = 0;
      else
      {
        if (degree <= i && i <= nbPoints)
          intervaleu[i] = i - degree + 1;
        else if (i > nbPoints)
          intervaleu[i] = nbPoints - degree + 2;
      }
    }

    return intervaleu;
  }

  private static Vector3 bSplineValue(int[] intervals, int degree, float value, Transform[] arrayPos)
  {
    Vector3 result = new Vector3(0, 0, 0);

    for (int i = 0; i < arrayPos.Length; i++)
    {
      float compute = computePoint(i, degree, intervals, value);
      
      result = result + arrayPos[i].position * compute;
    }  

    return result;
  }

  private static Vector3 bSplineValue(int[] intervals, int degree, float value, Vector3[] arrayPos)
  {
      Vector3 result = new Vector3(0, 0, 0);

      for (int i = 0; i < arrayPos.Length; i++)
      {
          float compute = computePoint(i, degree, intervals, value);

          result = result + arrayPos[i] * compute;
      }

      return result;
  }

  private static float computePoint(int step, int degree, int[] intervals, float value)
  {
    float result = 0;
    
    if (degree == 1)
    {
      
      if (intervals[step] <= value && value < intervals[step + 1])
        result =  1;
      else
        result = 0;

    }

    else
    {
      if (intervals[step + degree - 1] == intervals[step] && intervals[step + degree] == intervals[step + 1])
        result = 0;
      else if (intervals[step + degree - 1] == intervals[step])
        result = (intervals[step + degree] - value) / (intervals[step + degree] - intervals[step + 1]) * computePoint(step + 1, degree - 1, intervals, value);
      else if (intervals[step + degree] == intervals[step + 1])
        result = (value - intervals[step]) / (intervals[step + degree - 1] - intervals[step]) * computePoint(step, degree - 1, intervals, value);
      else
      {
        result = (value - intervals[step]) / (intervals[step + degree - 1] - intervals[step]) * computePoint(step, degree - 1, intervals, value) + (intervals[step + degree] - value) / (intervals[step + degree] - intervals[step + 1]) * computePoint(step + 1, degree - 1, intervals, value);
        //Debug.Log(value);
      }
    
    }


    return result;
  }

}
